【單元測試的藝術】Chap 8: 好的單元測試的支柱


目錄

  • PART I: 入門
    • Chap 1: 單元測試基礎
    • Chap 2: 第一個單元測試
  • PART II: 核心技術
    • Chap 3: 透過虛設常式解決依賴問題
    • Chap 4: 使用模擬物件驗證互動
    • Chap 5: 隔離(模擬)框架
    • Chap 6: 深入了解隔離框架
  • PART III: 測試程式碼
    • Chap 7: 測試階層和組織
    • Chap 8: 好的單元測試的支柱
    • Chap 9: 在組織中導入單元測試
    • Chap 10: 遺留程式碼
    • Chap 11: 設計與可測試性

前言

好的單元測試:

  • 可信賴
  • 可維護
  • 易讀

一、撰寫可信任的測試

原則和技術:

  1. 決定何時刪除或修改測試
    • 何時:
      • 產品 bug
      • 測試 bug
      • 語意或 API 有變更(功能不變)
      • 矛盾或無效的測試
    • 如果測試和產品程式沒有問題,會需要修改的原因
      • 重新命名或重構
      • 去除重複程式碼
  2. 避免測試中帶著邏輯
    • 不該 包含:
      • switch, if, else 語句
      • foreach, for, while 迴圈
  3. 一次只測試一個關注點
    • 一個關注點,是一個工作單元的最終結果:
      • 回傳值
      • 系統狀態的改變
      • 對第三方物件的互動
    • 否則容易導致
      • 難以命名
      • 可讀性降低
  4. 把單元測試和整合測試分開
    • 如上一章節所說的綠色安全區域,讓測試更容易執行
  5. 推行程式碼審查(確保覆蓋率)
    • 程式碼審查:兩個人坐在一起交談,現場查看或修改同一段程式碼
      • 並非追求 100% 程式覆蓋率
      • 覆蓋率低於 20% 代表缺少很多測試

二、撰寫可維護的測試

  1. 測試私有或保護的方法
    • 讓方法變成公開方法
    • 把方法抽取到新類別中
    • 把方法改成靜態方法
    • 把方法改為內部(internal)方法
  2. 去除重複程式碼
    • 使用輔助方法來去除重複程式碼:如工廠模式
    • 使用 [SetUp] 去除重複程式碼
  3. 具可維護性的設計來使用 Setup 方法:Setup 用起來很容易,但也易導致測試可讀性和可維護性下降
    • Setup 濫用
      • 初始化只在某些測試中用到的物件
      • 冗長難懂的 Setup 方法
      • 在 Setup 方法中準備假物件
    • 結論:停止使用 Setup 方法,多利用工廠和輔助方法。
  4. 實作隔離測試:
    • 強制的測試順序:必須依照特定順序執行
    • 存在呼叫其他測試的隱藏動作:測試呼叫其他測試
    • 共享狀態損壞:測試共享記憶體裡面的某個狀態,卻沒有重置
    • 外部共享狀態損壞:整合測試共享資源,卻沒有重置
  5. 避免對不同的關注點進行多次驗證
    • 使用參數化測試:[TestCase]
    • 使用 try-catch
  6. 物件比較:
    • 提高測試可維護性:建立一個用來比較的物件
    • 覆寫 ToString():
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < this.fields.Length; i++)
            {
                sb.Append(this[i]);
                sb.Append(".");
            }
            return sb.ToString()
        }
      
  7. 避免過度指定
    • 測試對一個被測試物件的純內部狀態進行驗證
    • 測試中使用多個模擬物件
    • 測試在需要使用虛設常式物件時,使用模擬物件
    • 測試在不必要的情況下,指定順序或使用了精準的參數匹配器

三、撰寫可讀性高的測試

可讀性有以下幾個方面:

  1. 命名單元測試
    • 被測試方法名稱:
      • 測試情境:說明了使用測試的條件;「如果我用一個 null 值來呼叫方法 X,那麼它應該執行 Y」
      • 預期行為:基於目前的情境,方法應該產生的行為結果或回傳值或行為方式;「如果我用一個 null 呼叫方法 X,那麼它應該執行 Y」
    • 如:MethodUnderTest_Sceneario_Beharvior()
  2. 命名變數
    • 如:Assert.AreEqual(COULD_NOT_READ_FILE, result)
  3. 使用好的驗證資訊
    • 不要重複測試框架的控制命令輸出的資訊
    • 不要重複測試名稱裡面包含的資訊
    • 如果沒有什麼有用的資訊,就什麼也別說
    • 提供關於什麼應該發生和什麼沒有發生的資訊,如果可能的話,提供應該發生的時間點
  4. 把驗證和操作分開
    • Bad Assert
        [Test]
        public void BadAssertMessage()
        {
            // 一些程式碼
            Assert.AreEqual(COULD_NOT_READ_FILE, log.GetLineCount("abc.txt");
        }
      
    • Good Assert
        [Test]
        public void GoodAssertMessage()
        {
            // 一些程式碼
            int result = log.GetLineCount("abc.txt");
            Assert.AreEqual(COULD_NOT_READ_FILE, result);
        }
      

小結

優秀的單元測試三大支柱:可讀性、可維護性、可信任性。重點是測試要隨著被測試系統一同成長和變化

#Unit Test #單元測試的藝術







你可能感興趣的文章

漏洞與修補留言板 XSS 和 SQL Injection

漏洞與修補留言板 XSS 和 SQL Injection

.Net MVC authorization Controller and Workcontext extension in razor view

.Net MVC authorization Controller and Workcontext extension in razor view

1. 開始java 17前,我們先快速了解一下spring boot

1. 開始java 17前,我們先快速了解一下spring boot






留言討論